//
//  DataAccessManager.swift
//  APIHeartRate
//
//  Created by Tek on 2023/5/3.
//

import Foundation

struct DetailSummary {
    let stepLow: Int
    let spRate: Int
    let dlRate: Int
    let highRate: Int
    let lowRate: Int
    let pressure: Int
    let oxgen: Int
    let dailyRateTime: Int
    let sportRateTime: Int
    let accRate: Int

    init(step: Int, sp: Int, dl: Int, max: Int, min: Int, pre: Int, ox: Int, dlTime: Int, spTime: Int) {
        stepLow = step
        spRate = sp
        dlRate = dl
        highRate = max
        lowRate = min
        pressure = pre
        oxgen = ox
        dailyRateTime = dlTime
        sportRateTime = spTime
        var temAcc = 0
        if dlTime != 0xFFFF && dl != 0xFF {
            temAcc += dlTime * dl
        }
        if spTime != 0xFFFF && sp != 0xFF {
            temAcc += spTime * sp
        }
        accRate = temAcc
    }
}

struct RecordSummary {
    let startDateStamp: Double
    var details: [DetailSummary]
}

struct StateRecord {
    let startTime: Double
    var states: [Int]
}

struct HistoryDetail {
    let start: String
    let end: String
    let type: Int
    let sleepType: Int
    let steps: Int
    let heartRate: Int
    let pressure: Int
    let oxgen: Int
    let sleepTime: Int
}

struct HistoryRecord {
    let startTime: Double
    var details: [HistoryDetail]
}

struct SleepSeg {
    let type: UInt8
    let dateStr: String
}

struct SleepItem {
    let dateStr: String
    let segs: Int
    let total: Int
    let low: Int
    let deep: Int
    let datas: [SleepSeg]
}

class DataAccessManager {
    static let instance = DataAccessManager()

    public var page = 0
    public var total = 0
    public var dataType: FetchDataType = .pressure

    public var isSport = false

    private var startDate: Date = Date()
    private var endDate: Date = Date()
    private var hourDate: Date = Date()

    var currentStamp: Double = 0

    lazy var dateFmt: DateFormatter = {
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd HH:mm:ss"
        df.locale = Locale.init(identifier: "en_US_POSIX")
        return df
    }()

    private var record: [RecordSummary] = []
    private var detailBuffer: [DetailSummary] = []
    private var dataBuffer: Data = Data()
    private var rowDataRecored: [[Double: Data]] = []

    private var historyRecord: [HistoryRecord] = []
    private var historyBuffer: [HistoryDetail] = []

    private var sleepArray: [SleepItem] = []
    

    public func clearData() {
        page = 0
        dataBuffer = Data()
        detailBuffer = []
        rowDataRecored = []
        total = 0
        record = []
        historyRecord = []
        historyBuffer = []
        dataBuffer.removeAll()
        sleepArray = []
    }

    public func updateData(data: Data) {
        if (data.count > 1) {
            dataBuffer.append(data.dropFirst(1))
        }
        page += (data.count - 1)
        if page <= 0 || total == 0{
            return
        }

        if page % total == 0 {
            parseData()
        }
    }
    
    func getRawData() -> String {
        let str = dataBuffer.map { String.init(format: "%02x", $0) }.joined(separator: " ")
        return "[\(str)]"
    }

    private func parseData() {
        switch dataType {
        case .sleep:
            parseSleepData()
        case .excercise:
            parseExcercoseData()
        case .motionDaily:
            parseMotionDailyData()
        case .motionSport:
            parseMotionSportData()
        default:
            parseNormalData()
        }
    }

    private func fileterValue(_ source: [UInt8]) -> Int {
        if source.count == 0 {
            return 0
        }
        if source.count == 1 {
            return Int(source[0]) == 0xFF ? 0 : Int(source[0])
        }
        if source.count == 2 {
            let v = Int(Int32(source[0]) << 8 | Int32(source[1]))
            return v == 0xFFFF ? 0 : v
        }
        return 0
    }

    private func parseNormalData() {
        var offset = 0
        let total = dataBuffer.count
        while offset < total {
            let stampData = Data(dataBuffer[offset ..< (offset + 4)])
            currentStamp = Double(Int32(stampData[0]) | Int32(stampData[1]) << 8 | Int32(stampData[2]) << 16 | Int32(stampData[3]) << 24)
            offset += 4
            while detailBuffer.count < 24 && offset + 20 <= total {
                let dDetail = Data(dataBuffer[offset ..< (offset + 20)])
                detailBuffer.append(
                    DetailSummary(
                        step: fileterValue([dDetail[0], dDetail[1]]),
                        sp: fileterValue([dDetail[7]]),
                        dl: fileterValue([dDetail[9]]),
                        max: fileterValue([dDetail[14]]),
                        min: fileterValue([dDetail[15]]),
                        pre: fileterValue([dDetail[16]]),
                        ox: fileterValue([dDetail[17]]),
                        dlTime: fileterValue([dDetail[10], dDetail[11]]),
                        spTime: fileterValue([dDetail[18], dDetail[19]])))
                offset += 20
            }
            record.append(RecordSummary(startDateStamp: currentStamp, details: detailBuffer))
            detailBuffer = []
        }
    }

    public func parseSleepData() {
        
        let pages = dataBuffer.count / 191
        for i in 0 ..< pages {
            if ((i + 1) * 191 > dataBuffer.count) {
                continue
            }
            let stampData = Data(dataBuffer[i * 191 ..< ((i + 1) * 191)])
            if (stampData.count < 191) {
                continue
            }
            currentStamp = Double(Int32(stampData[0]) | Int32(stampData[1]) << 8 | Int32(stampData[2]) << 16 | Int32(stampData[3]) << 24)
            if (currentStamp == 0xFFFFFFFF) {
                continue
            }
            let sleepAll = Int16(stampData[4]) | Int16(stampData[5]) << 8
            let sleepLow = Int16(stampData[6]) | Int16(stampData[7]) << 8
            let sleepDeep = Int16(stampData[8]) | Int16(stampData[9]) << 8
            let segAll = Int(stampData[10])

            let segDatas = Data(stampData[11 ..< (11 + 3 * segAll)])
            let startDate = Date(timeIntervalSince1970: currentStamp)
            let startDate2 = startDate.addingTimeInterval(24 * 60 * 60)
            let calendar = Calendar.current
            let year = calendar.component(.year, from: startDate)
            let year2 = calendar.component(.year, from: startDate2)
            let month = calendar.component(.month, from: startDate)
            let month2 = calendar.component(.month, from: startDate2)
            let day = calendar.component(.day, from: startDate)
            let day2 = calendar.component(.day, from: startDate2)

            var tempSeg: [SleepSeg] = []
            var curIndex = 0
            while curIndex <= segDatas.count - 3 {
                let h = Int(segDatas[curIndex])
                let m = Int(segDatas[curIndex + 1])
                tempSeg.append(SleepSeg(type: UInt8(segDatas[curIndex + 2]),
                                        dateStr: getRightStr(y: year, y1: year2, m: month, m1: month2, d: day, d1: day2, h: h, mi: m)))
                curIndex += 3
            }

            sleepArray.append(SleepItem(dateStr:dateFmt.string(from: startDate) ,segs: segAll, total: Int(sleepAll), low: Int(sleepLow), deep: Int(sleepDeep), datas: tempSeg))
        }
        
            // getFinePrint(getSummarySleep())
    }
    
    func getFinePrint(_ info: [[String : Any]]) {
        do {
            let Json = try JSONSerialization.data(withJSONObject: info , options: .prettyPrinted)
            print(String(data: Json, encoding: .utf8) ?? "")
        } catch {
            print(info.description)
        }
    }
    
    private func getRightStr(y: Int, y1: Int, m: Int, m1: Int, d: Int, d1: Int, h: Int, mi: Int) -> String {
        return h >= 20 ? String.init(format: "%04d-%02d-%02d %02d:%02d:00", y, m, d , h, mi) : String.init(format: "%04d-%02d-%02d %02d:%02d:00", y1, m1, d1 , h, mi)
    }

    private func parseExcercoseData() {
        var offset = 0
        let total = dataBuffer.count
        while offset < total - 4 {
            let stampData = Data(dataBuffer[offset ..< (offset + 4)])
            currentStamp = Double(Int32(stampData[0]) | Int32(stampData[1]) << 8 | Int32(stampData[2]) << 16 | Int32(stampData[3]) << 24)
            offset += 4
            var temp: [Double: Data] = [:]
            if offset + 288 <= total {
                temp[currentStamp] = Data(dataBuffer[offset ..< offset + 288])
                offset += 288
            }
            rowDataRecored.append(temp)
        }
    }

    private func parseMotionDailyData() {
        var offset = 0
        let total = dataBuffer.count
        var lastTime = 0.0
        while offset + 13 <= total {
            let sub = Data(dataBuffer[offset ..< offset + 13])
            offset += 13
            let start = Double(Int32(sub[0]) | Int32(sub[1]) << 8 | Int32(sub[2]) << 16 | Int32(sub[3]) << 24)
            if sub[5] == 0xFF {
                continue
            }
            if (start <= lastTime) {
                continue
            }
            
            lastTime = start
            
            historyBuffer.append(
                HistoryDetail(
                    start: dateFmt.string(from: Date(timeIntervalSince1970: start - 299)),
                    end: dateFmt.string(from: Date(timeIntervalSince1970: start)),
                    type: Int(sub[4]),
                    sleepType: Int(sub[5]),
                    steps: fileterValue([sub[6], sub[7]]),
                    heartRate: fileterValue([sub[8]]),
                    pressure: fileterValue([sub[9]]),
                    oxgen: fileterValue([sub[10]]),
                    sleepTime: fileterValue([sub[11], sub[12]])))
        }
    }

    private func parseMotionSportData() {
        var offset = 0
        let total = dataBuffer.count
        while offset < total - 4 {
            let stampData = Data(dataBuffer[offset ..< (offset + 4)])
            currentStamp = Double(Int32(stampData[0]) | Int32(stampData[1]) << 8 | Int32(stampData[2]) << 16 | Int32(stampData[3]) << 24)
            offset += 4
            var len = 0
            while len < 60 && offset + 2 <= total {
                let sub = Data(dataBuffer[offset ..< offset + 2])
                offset += 2
                len += 1
                let ht = Int(sub[1])
                if ht == 0xFF {
                    continue
                }
                historyBuffer.append(HistoryDetail(
                    start: "",
                    end: "",
                    type: Int(sub[0]) % 16,
                    sleepType: 0,
                    steps: 0,
                    heartRate: ht,
                    pressure: 0, oxgen: 0, sleepTime: 0)
                )
            }
            historyRecord.append(HistoryRecord(startTime: currentStamp, details: historyBuffer))
            historyBuffer.removeAll()
        }
    }

    func mapTimeInterval(data: Data) -> Double {
        if data.count < 5 {
            return 0
        }
        var time: Int32 = 0
        var i = 0
        while i < 4 {
            time |= Int32(data[i + 1]) << (8 * i)
            i += 1
        }
        return Double(time)
    }

    func getSummaryHeartRate() -> [[String: Any]] {
        var ret: [[String: Any]] = []

        for day in record {
            let start = Date(timeIntervalSince1970: day.startDateStamp)
            let end = start.addingTimeInterval(24 * 60 * 60 - 1)

            var mx = 0
            var mi = 0
            var cum = 0
            var avD = 0.0
            var avS = 0.0
            var dTime = 0
            var sTime = 0
            day.details.forEach { item in
                if item.highRate > 0 {
                    mx = max(item.highRate, mx)
                }

                if item.lowRate > 0 {
                    mi = mi == 0 ? item.lowRate : min(item.lowRate, mi)
                }
                cum += item.accRate

                if item.dlRate > 0 {
                    avD = (avD + Double(item.dlRate)) / 2.0
                }

                if item.spRate > 0 {
                    avS = (avS + Double(item.spRate)) / 2.0
                }
                dTime += item.dailyRateTime
                sTime += item.sportRateTime
            }
            let sum = Double(dTime + sTime)
            let avRa = Float(Double(cum) / (sum == 0 ? 1.0 : sum))
            ret.append([
                "startDate": dateFmt.string(from: start),
                "endDate": dateFmt.string(from: end),
                "highestHeartRate": mx,
                "lowestHeartRate": mi,
                "averageHeartRate": avRa,
                "dailyAverageHeartRate": Float(avD),
                "sportAverageHeartRate": Float(avS),
                "heartRateTotalTime": dTime + sTime,
                "dailyHeartRateTotalTime": dTime,
                "sportHeartRateTotalTime": sTime,
            ])
        }
        return ret as [[String: Any]]
    }

    private func valuePerHour(start: Date, value: Any, for key: String, hour: Int) -> [String: Any] {
        let s = start.addingTimeInterval(TimeInterval(hour * 60 * 60))
        let e = s.addingTimeInterval(59 * 60)
        return [
            "startDate": dateFmt.string(from: s),
            "endDate": dateFmt.string(from: e),
            key: value,
        ]
    }

    func getStepSummary() -> [[String: Any]] {
    
        
        var ret: [[String: Any]] = []
        for day in record {
            let start = Date(timeIntervalSince1970: day.startDateStamp)
            let end = start.addingTimeInterval(24 * 60 * 60 - 1)

            var hour = 0
            var stepsByHour: [[String: Any]] = []
            let steps = day.details.reduce(0) { partialResult, detail in
                stepsByHour.append(valuePerHour(start: start, value: detail.stepLow, for: "steps", hour: hour))
                hour += 1
                return partialResult + detail.stepLow
            }
            ret.append([
                "startDate": dateFmt.string(from: start),
                "endDate": dateFmt.string(from: end),
                "steps": steps,
                "stepsByHour": stepsByHour,
            ])
        }
        return ret as [[String: Any]]
    }

    func getSummaryBloodOxygen() -> [[String: Any]] {
        
        var ret: [[String: Any]] = []

        for day in record {
            let start = Date(timeIntervalSince1970: day.startDateStamp)
            let end = start.addingTimeInterval(24 * 60 * 60 - 1)

            var mxOxgen = 0
            var miOxgen = 0
            var avOxgen = 0.0
            day.details.forEach { item in
                if item.oxgen > 0 {
                    if miOxgen == 0 {
                        miOxgen = item.oxgen
                    }
                    mxOxgen = max(item.oxgen, mxOxgen)
                    miOxgen = min(item.oxgen, miOxgen)
                    avOxgen = (avOxgen + Double(item.oxgen)) / 2.0
                }
            }
            ret.append([
                "startDate": dateFmt.string(from: start),
                "endDate": dateFmt.string(from: end),
                "highestBloodOxygen": mxOxgen,
                "lowestBloodOxygen": miOxgen,
                "averageBloodOxygen": avOxgen,
            ])
        }
        return ret as [[String: Any]]
    }

    func getSummaryPressureIndex() -> [[String: Any]] {
        
        var ret: [[String: Any]] = []
        for day in record {
            let start = Date(timeIntervalSince1970: day.startDateStamp)
            let end = start.addingTimeInterval(24 * 60 * 60 - 1)

            var mxPre = 0
            var miPre = 200
            var avPre = 0.0
            day.details.forEach { item in
                if item.pressure > 0 {
                    if miPre == 0 {
                        miPre = item.pressure
                    }
                    mxPre = max(item.pressure, mxPre)
                    miPre = min(item.pressure, miPre)
                    avPre = (avPre + Double(item.pressure)) / 2.0
                }
            }

            ret.append([
                "startDate": dateFmt.string(from: start),
                "endDate": dateFmt.string(from: end),
                "highestPressureIndex": mxPre,
                "lowestPressureIndex": miPre,
                "averagePressureIndex": avPre,
            ])
        }
        return ret as [[String: Any]]
    }

    func getSummaryCalorie() -> [[String: Any]] {
        
        var ret: [[String: Any]] = []

        for day in record {
            let start = Date(timeIntervalSince1970: day.startDateStamp)
            let end = start.addingTimeInterval(24 * 60 * 60 - 1)

            var hour = 0
            var calorieByHour: [[String: Any]] = []
            let calorieTotal = day.details.reduce(0.0) { partialResult, detail in
                // 千卡路里 = 60 * 距离(步数 * 0.55)(千米) * 1.036 / 1000
                let calories: Double = Double(detail.stepLow) * 0.55 * 60 * 1.036 / 1000
                calorieByHour.append(valuePerHour(start: start, value: calories, for: "calorie", hour: hour))
                hour += 1
                return partialResult + calories
            }

            ret.append([
                "startDate": dateFmt.string(from: start),
                "endDate": dateFmt.string(from: end),
                "totalCalorie": calorieTotal,
                "calorieByHour": calorieByHour,
            ])
        }
        return ret as [[String: Any]]
    }

    func getSummarySleep() -> [[String: Any]] {
        
        var ret: [[String: Any]] = []
        for slp in sleepArray {
            var awake: [[String: String]] = []
            var light: [[String: String]] = []
            var deep: [[String: String]] = []
            
            var front = 0
            var end = 0
            var lastOne = ""
            while(end < slp.datas.count) {
                
                let itemA = slp.datas[front]
                let itemB = slp.datas[end]
                
                //最后一个数据
                if (end + 1 == slp.datas.count) {
                    let item = [
                        "startDate": itemA.dateStr,
                        "endDate": itemB.dateStr,
                    ]
                    if itemA.type == 0x00 {
                        awake.append(item)
                    } else if itemA.type == 0x01 {
                        light.append(item)
                    } else if itemA.type == 0x02 {
                        deep.append(item)
                    }
                    lastOne = itemB.dateStr
                    break
                }
                
               
                if (itemA.type == itemB.type) {
                    end += 1
                    continue
                }
                
                let item = [
                    "startDate": itemA.dateStr,
                    "endDate": itemB.dateStr,
                ]
                if itemA.type == 0x00 {
                    awake.append(item)
                } else if itemA.type == 0x01 {
                    light.append(item)
                } else if itemA.type == 0x02 {
                    deep.append(item)
                }
                front = end
                end += 1
                
            }
            var earlyOne = light.first?["startDate"] ?? ""
            if earlyOne == "" {
                earlyOne = deep.first?["startDate"] ?? ""
            }
            ret.append([
                "startDate": earlyOne,
                "endDate": lastOne,
                "awakeTime": awake,
                "lightSleepTime": light,
                "deepSleepTime": deep,
                "sleepSum": slp.total,
                "lowSum": slp.low,
                "deepSum": slp.deep,
            ])
        }
        return ret as [[String: Any]]
    }

    func getExerciseData() -> [[String: Any]] {

        
        var ret: [[String: Any]] = []
        var currentState: UInt8 = 0x05
        for dict in rowDataRecored {
            for (key, value) in dict {
                let start = Date(timeIntervalSince1970: key)

                var noExcercise: [[String: String]] = []
                var light: [[String: String]] = []
                var heavy: [[String: String]] = []

                var left: Int = 0
                var right: Int = 0
                let len = value.count
                for span in value {
                    if right == len - 1 {
                        right += 1
                    }
                    if span == currentState && len - 1 > right {
                        right += 1

                    } else {
                        if currentState == 0x05 {
                            currentState = span
                            right += 1
                        } else {
                            let item = [
                                "startDate": dateFmt.string(from: start.addingTimeInterval(TimeInterval(left * 60 * 5))),
                                "endDate": dateFmt.string(from: start.addingTimeInterval(TimeInterval(right * 60 * 5))),
                            ]
                            if currentState == 0 {
                                noExcercise.append(item)
                            } else if currentState == 1 {
                                light.append(item)
                            } else if currentState == 2 {
                                heavy.append(item)
                            }

                            left = right
                            right += 1
                            currentState = span
                        }
                    }
                }
                ret.append([
                    "noExerciseTime": noExcercise,
                    "lightExerciseTime": light,
                    "heavyExerciseTime": heavy,
                ])
            }
        }

        return ret as [[String: Any]]
    }

    func getMotionData() -> [[String: Any]] {
        
        if isSport {
            return getSportHistoryData()
        }

        return getDailyHistoryData()
    }

    func getDailyHistoryData() -> [[String: Any]] {
        var ret: [[String: Any]] = []
        for item in historyBuffer {
            ret.append([
                "seq": ret.count,
                "startDate": item.start,
                "endDate": item.end,
                "sportType": 0,
                "sportLevel": item.type,
                "heart": item.heartRate,
                "step": item.steps,
                "pressure": item.pressure,
                "oxgen": item.oxgen
            ])
        }
        return ret as [[String: Any]]
    }

    func getSportHistoryData() -> [[String: Any]] {
        var ret: [[String: Any]] = []
        for rd in historyRecord {
            for (index, item) in rd.details.enumerated() {
                let start = Date(timeIntervalSince1970: rd.startTime).addingTimeInterval(TimeInterval(index * 1))
                let startStr = dateFmt.string(from: start)
                ret.append([
                    "seq": ret.count,
                    "startDate": startStr,
                    "endDate": startStr,
                    "sportType": 1,
                    "sportLevel": item.type,
                    "heart": item.heartRate,
                ])
            }
        }
        return ret as [[String: Any]]
    }

    func maoV(type: Int) -> Int {
        switch type {
        case 0:
            return getRandomInt(from: 200, to: 4000)
        case 1:
            return 60
        case 2:
            return getRandomInt(from: 50, to: 140)
        case 3:
            return getRandomInt(from: 0, to: 100)
        case 4:
            return getRandomInt(from: 20, to: 100)
        default:
            break
        }
        return 0
    }

    func getRandomInt(from: Int, to: Int) -> Int {
        return Int.random(in: from ... to)
    }
}
